﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using WooCoo.Data;

namespace WooCoo.Tools
{
    public static class TypeHelper
    {

        public static object GetDefaultValue(Type targetType)
        {
            if (targetType.IsValueType) return Activator.CreateInstance(targetType);
            return null;
        }

        #region  CoerceValue

        /// <summary>
        /// 强制把 value 的值变换为 desiredType
        /// </summary>
        /// <param name="desiredType"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static object CoerceValue(Type desiredType, object value)
        {
            if (value != null)
            {
                value = CoerceValue(desiredType, value.GetType(), value);
            }
            else
            {
                value = TypeHelper.GetDefaultValue(desiredType);
            }

            return value;
        }

        /// <summary>
        /// Attempts to coerce a value of one type into
        /// a value of a different type.
        /// </summary>
        /// <param name="desiredType">
        /// Type to which the value should be coerced.
        /// </param>
        /// <param name="valueType">
        /// Original type of the value.
        /// </param>
        /// 
        /// <param name="value">
        /// The value to coerce.
        /// </param>
        /// <remarks>
        /// <para>
        /// If the desired type is a primitive type or Decimal, 
        /// empty string and null values will result in a 0 
        /// or equivalent.
        /// </para>
        /// <para>
        /// If the desired type is a Nullable type, empty string
        /// and null values will result in a null result.
        /// </para>
        /// <para>
        /// If the desired type is an enum the value's ToString()
        /// result is parsed to convert into the enum value.
        /// </para>
        /// </remarks>
        public static object CoerceValue(Type desiredType, Type valueType, object value)
        {
            // types match, just return value
            if (desiredType.IsAssignableFrom(valueType)) { return value; }

            if (desiredType.IsGenericType && desiredType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                desiredType = Nullable.GetUnderlyingType(desiredType);

                if (value == null ||
                    valueType.Equals(typeof(string)) && Convert.ToString(value) == string.Empty) return null;
            }

            if (desiredType.IsEnum)
            {
                return Enum.Parse(desiredType, value.ToString());
            }

            if ((desiredType.IsPrimitive || desiredType.Equals(typeof(decimal))) &&
                valueType.Equals(typeof(string)) && string.IsNullOrEmpty((string)value))
                value = 0;

            if (desiredType == typeof(Guid) && value is string) { return Guid.Parse(value as string); }

            try
            {
                if (desiredType.Equals(typeof(string)) && value != null) return value.ToString();

                return Convert.ChangeType(value, desiredType);
            }
            catch
            {
                var cnv = TypeDescriptor.GetConverter(desiredType);
                if (cnv != null && cnv.CanConvertFrom(valueType)) return cnv.ConvertFrom(value);

                throw;
            }
        }

        /// <summary>
        /// Attempts to coerce a value of one type into
        /// a value of a different type.
        /// </summary>
        /// <typeparam name="T">
        /// Type to which the value should be coerced.
        /// </typeparam>
        /// <param name="valueType">
        /// Original type of the value.
        /// </param>
        /// 
        /// <param name="value">
        /// The value to coerce.
        /// </param>
        /// <remarks>
        /// <para>
        /// If the desired type is a primitive type or Decimal, 
        /// empty string and null values will result in a 0 
        /// or equivalent.
        /// </para>
        /// <para>
        /// If the desired type is a Nullable type, empty string
        /// and null values will result in a null result.
        /// </para>
        /// <para>
        /// If the desired type is an enum the value's ToString()
        /// result is parsed to convert into the enum value.
        /// </para>
        /// </remarks>
        public static T CoerceValue<T>(Type valueType, object value)
        {
            return (T)(CoerceValue(typeof(T), valueType, value));
        }

        public static T CoerceValue<T>(object value)
        {
            if (value == null) throw new ArgumentNullException("value");

            return (T)(CoerceValue(typeof(T), value.GetType(), value));
        }

        #endregion

        public static Type GetElementType(Type seqType)
        {
            Type type = TypeHelper.FindIEnumerable(seqType);
            if (type == null)
            {
                return seqType;
            }
            return type.GetGenericArguments()[0];
        }

        public static Type FindIEnumerable(Type seqType)
        {
            if (seqType == null || seqType == typeof(string))
            {
                return null;
            }
            if (seqType.IsArray)
            {
                return typeof(IEnumerable<>).MakeGenericType(new Type[]
				{
					seqType.GetElementType()
				});
            }
            if (seqType.IsGenericType)
            {
                Type[] genericArguments = seqType.GetGenericArguments();
                for (int i = 0; i < genericArguments.Length; i++)
                {
                    Type type = genericArguments[i];
                    Type type2 = typeof(IEnumerable<>).MakeGenericType(new Type[]
					{
						type
					});
                    if (type2.IsAssignableFrom(seqType))
                    {
                        Type result = type2;
                        return result;
                    }
                }
            }
            Type[] interfaces = seqType.GetInterfaces();
            if (interfaces != null && interfaces.Length > 0)
            {
                Type[] array = interfaces;
                for (int j = 0; j < array.Length; j++)
                {
                    Type seqType2 = array[j];
                    Type type3 = TypeHelper.FindIEnumerable(seqType2);
                    if (type3 != null)
                    {
                        Type result = type3;
                        return result;
                    }
                }
            }
            if (seqType.BaseType != null && seqType.BaseType != typeof(object))
            {
                return TypeHelper.FindIEnumerable(seqType.BaseType);
            }
            return null;
        }
        /// <summary>Checks whether the specified type is a generic nullable type.</summary>
        /// <param name="type">Type to check.</param>
        /// <returns>true if <paramref name="type"/> is nullable; false otherwise.</returns>
        public static bool IsNullableType(Type type)
        {
            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
        }

        /// <summary>
        /// 是否为相互继承
        /// </summary>
        /// <param name="baseType"></param>
        /// <param name="derivedType"></param>
        /// <returns></returns>
        public static bool IsTypeOfGenericBaseType(Type baseType, Type derivedType)
        {
            while (derivedType != null && derivedType != typeof(object))
            {
                var currentType = derivedType.IsGenericType ? derivedType.GetGenericTypeDefinition() : derivedType;
                if (currentType == baseType)
                {
                    return true;
                }

                derivedType = derivedType.BaseType;
            }

            return false;
        }

        /// <summary>
        /// 判断类型或者元素类型是否为Entity类型
        /// </summary>
        /// <param name="type">Type to examine</param>
        /// <returns>bool indicating whether or not entity type</returns>
        internal static bool TypeOrElementTypeIsEntity(Type type)
        {
            type = TypeHelper.GetElementType(type);
            type = Nullable.GetUnderlyingType(type) ?? type;
            return type.GetCustomAttributes(false).OfType<EntityKeyAttribute>().FirstOrDefault() != null;
            //return !PrimitiveType.IsKnownType(type) && ClientTypeUtil.GetKeyPropertiesOnType(type) != null;
        }
        /// <summary>
        /// 类型是否为实体类型
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        internal static bool TypeIsEntity(Type type)
        {
            return type.GetCustomAttributes(false).OfType<EntityKeyAttribute>().Any();
        }

        /// <summary>
        /// Looks at the passed in type and trys to resolve the friendly name via DataAnnotations or the ComponentModel attribute.
        /// 
        /// Note: This code should not throw any exceptions because the property must exist!
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="name">The name of the property.</param>
        /// <returns>Returns the display name or the property name.</returns>
        public static string GetFriendlyNameFromAttributes(PropertyInfo propertyInfo)
        {
            string name = propertyInfo.Name;
            // DataAnnotations attribute.
            var display = propertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true).OfType<DisplayAttribute>().FirstOrDefault();
            if (display != null)
                name = display.GetName();
            else
            {
                // ComponentModel attribute.
                var displayName = propertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), true).OfType<DisplayNameAttribute>().FirstOrDefault();
                if (displayName != null)
                    name = displayName.DisplayName;
            }
            return name;
        }

        #region 获取类型的默认值
        public static object GetDefaultValueForType(Type type)
        {
            if (type == null)
            {
                throw Error.ArgumentNull("type");
            }
            if (type.IsValueType)
            {
                return Activator.CreateInstance(type);
            }
            return null;
        }
        #endregion
    }
}
